using System;
using System.Diagnostics;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media.Media3D;
using System.Windows.Markup;

namespace SBPweb.Presentation.CompleteMesh.Utils
{

    public class Trackball
    {

        #region Private members

        private FrameworkElement pEventSource;
        private Point pPreviousPosition2D;
        private Vector3D pPreviousPosition3D = new Vector3D(0, 0, 1);

        private Transform3DGroup pTransform;
        private ScaleTransform3D pScale = new ScaleTransform3D();
        private AxisAngleRotation3D pRotation = new AxisAngleRotation3D();

        #endregion

        #region Constructor logic

        public Trackball()
        {
            pTransform = new Transform3DGroup();
            pTransform.Children.Add(pScale);
            pTransform.Children.Add(new RotateTransform3D(pRotation));
        }

        #endregion


        #region Public property accessors

        public Transform3D Transform
        {
            get
            {
                return pTransform;
            }
        }

        public FrameworkElement EventSource
        {
            get
            {
                return pEventSource;
            }
            set
            {
                if (pEventSource != null)
                {
                    pEventSource.MouseDown -= this.OnMouseDown;
                    pEventSource.MouseUp -= this.OnMouseUp;
                    pEventSource.MouseMove -= this.OnMouseMove;
                }

                pEventSource = value;

                pEventSource.MouseDown += this.OnMouseDown;
                pEventSource.MouseUp += this.OnMouseUp;
                pEventSource.MouseMove += this.OnMouseMove;
            }
        }

        #endregion

        #region Event Handling

        private void OnMouseDown(object sender, MouseEventArgs e)
        {
            Mouse.Capture(EventSource, CaptureMode.Element);
            pPreviousPosition2D = e.GetPosition(EventSource);
            pPreviousPosition3D = ProjectToTrackball(
                EventSource.ActualWidth,
                EventSource.ActualHeight,
                pPreviousPosition2D);
        }

        private void OnMouseUp(object sender, MouseEventArgs e)
        {
            Mouse.Capture(EventSource, CaptureMode.None);
        }

        private void OnMouseMove(object sender, MouseEventArgs e)
        {
            Point currentPosition = e.GetPosition(EventSource);

            // Prefer tracking to zooming if both buttons are pressed.
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                Track(currentPosition);
            }
            else if (e.RightButton == MouseButtonState.Pressed)
            {
                Zoom(currentPosition);
            }

            pPreviousPosition2D = currentPosition;
        }

        #endregion Event Handling

        #region Private helper methods

        private void Track(Point currentPosition)
        {
            Vector3D currentPosition3D = ProjectToTrackball(
                EventSource.ActualWidth, EventSource.ActualHeight, currentPosition);

            Vector3D axis = Vector3D.CrossProduct(pPreviousPosition3D, currentPosition3D);
            double angle = Vector3D.AngleBetween(pPreviousPosition3D, currentPosition3D);
            Quaternion delta = new Quaternion(axis, -angle);

            // Get the current orientantion from the RotateTransform3D
            AxisAngleRotation3D r = pRotation;
            Quaternion q = new Quaternion(pRotation.Axis, pRotation.Angle);

            // Compose the delta with the previous orientation
            q *= delta;

            // Write the new orientation back to the Rotation3D
            pRotation.Axis = q.Axis;
            pRotation.Angle = q.Angle;

            pPreviousPosition3D = currentPosition3D;
        }

        private Vector3D ProjectToTrackball(double width, double height, Point point)
        {
            double x = point.X / (width / 2);    // Scale so bounds map to [0,0] - [2,2]
            double y = point.Y / (height / 2);

            x = x - 1;                           // Translate 0,0 to the center
            y = 1 - y;                           // Flip so +Y is up instead of down

            double z2 = 1 - x * x - y * y;       // z^2 = 1 - x^2 - y^2
            double z = z2 > 0 ? Math.Sqrt(z2) : 0;

            return new Vector3D(x, y, z);
        }

        private void Zoom(Point currentPosition)
        {
            double yDelta = currentPosition.Y - pPreviousPosition2D.Y;

            double scale = Math.Exp(yDelta / 100);    // e^(yDelta/100) is fairly arbitrary.

            pScale.ScaleX *= scale;
            pScale.ScaleY *= scale;
            pScale.ScaleZ *= scale;
        }

        #endregion

    }
}
